package net.wangds.utils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import net.wangds.log.helper.LogHelper;
import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class HttpUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtils.class);

    private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create();

    private static final String[] TEXT_YES = new String[]{"true", "t", "yes", "y", "ok", "enable","是"};

    @SuppressWarnings("unused")
    private static HttpLoggingInterceptor createLogInterceptor() {
        HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(
                message -> LogHelper.dev("okhttp3: " + message));//创建拦截对象
        logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);//这一句一定要记得写，否则没有数据输出
        return logInterceptor;
    }

    private static OkHttpClient createClient() {

        Properties prop = new Properties();
        try(InputStream is = new ClassPathResource("application-mod_okhttp.properties").getInputStream()){
            prop.load(is);
        } catch (IOException e) {
            LogHelper.error(e);
        }


        OkHttpClient.Builder b = new OkHttpClient.Builder();
        String opt_enable_logs = prop.getProperty("MOD.OKHTTP.LOGS","y");
        LogHelper.dev(String.format("options mod_okhttp logs:%s", opt_enable_logs));
        if(StringUtils.equalsAnyIgnoreCase(opt_enable_logs,
                TEXT_YES)) {
            b.addNetworkInterceptor(createLogInterceptor()); //开启日志
        }
        return b.build();
    }

    private static final OkHttpClient client = createClient();

    /**
     * GET方法访问url，并以字符串形式返回应答Body.
     *
     * @param url 目标url.
     * @return 应答字符串.
     * @throws IOException Http异常.
     */
    @SuppressWarnings("unused")
    public static String getString(String url) throws IOException {
        return getString(url, new HashMap<>());
    }

    /**
     * GET方法访问url，并以字符串形式返回应答Body.
     *
     * @param url     目标url.
     * @param headers 请求HTTP头.
     * @return 应答字符串.
     * @throws IOException Http异常.
     */
    public static String getString(String url, Map<String, String> headers) throws IOException {

        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .build();

        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            return rb.isPresent() ? rb.get().string() : "";
        }
    }

    /**
     * 下载简单的文件.
     * <p>该方法用于下载文件，只能通过InputStream的读取文件内容，不关心文件名.</p>
     *
     * @param url      url.
     * @param headers  请求头.
     * @param callback 应答读取方法.
     */
    public static void asyncHttpSimpleDownload(String url, Map<String, String> headers, Consumer<InputStream> callback) {
        asyncHttpDownload(url, headers, (resp, body) -> {
            try (InputStream is = body.byteStream()) {
                if (callback != null) {
                    callback.accept(is);
                }
            } catch (IOException e) {
                LogHelper.error(e);
            }
        }, null, null);
    }

    public static void asyncHttpDownload(String url, Map<String, String> headers, BiConsumer<Response, ResponseBody> bodyHandler, BiConsumer<Call, Response> onAnomaly, BiConsumer<Call, IOException> onTranslateError) {
        asyncGet(url, headers, (resp) -> {
            if (bodyHandler != null) {
                bodyHandler.accept(resp, resp.body());
            }
        }, onAnomaly, onTranslateError);
    }

    @SuppressWarnings("unused")
    public static void asyncHttpGet(String url, Map<String, String> headers, Consumer<ResponseBody> bodyHandler, BiConsumer<Call, Response> onAnomaly, BiConsumer<Call, IOException> onTranslateError) {
        asyncGet(url, headers, (resp) -> {
            if (bodyHandler != null) {
                bodyHandler.accept(resp.body());
            }
        }, onAnomaly, onTranslateError);
    }

    public static void asyncGet(String url, Map<String, String> headers, Consumer<Response> onCodeWas2xx, BiConsumer<Call, Response> onAnomaly, BiConsumer<Call, IOException> onTranslateError) {
        asyncGet(url, headers, (call, resp) -> {

            int code = resp.code();

            if (code >= 200 && code < 300) {
                //2xx是正常返回
                if (onCodeWas2xx != null) {
                    onCodeWas2xx.accept(resp);
                }
            } else {
                if (onAnomaly != null) {
                    onAnomaly.accept(call, resp);
                }
            }

        }, onTranslateError);
    }

    public static void asyncGet(String url, Map<String, String> headers, BiConsumer<Call, Response> onSuccess, BiConsumer<Call, IOException> onError) {
        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                if (onError != null) {
                    onError.accept(call, e);
                } else {
                    LogHelper.error(new RuntimeException("http failure: url=" + url + ",call:[" + call.toString() + "]", e));
                }
            }

            @Override
            public void onResponse(Call call, Response response) {
                if (onSuccess != null) {
                    onSuccess.accept(call, response);
                }
            }
        });
        /*
        try () {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            return rb.isPresent() ? rb.get().string() : "";
        }
        */
    }

    /**
     * 按照JSON格式解析GET请求返回应答体.
     * <p>
     * 调用方式：
     * getJson(url, map, new TypeToken&lt;A&lt;B&gt;&gt;(){});
     *
     * @param url       目标url.
     * @param headers   请求HTTP头.
     * @param typeToken 返回类型.
     * @param <T>       返回类型.
     * @return 返回值.
     * @throws IOException Http请求异常.
     */
    public static <T> T getJson(String url, Map<String, String> headers, TypeToken<T> typeToken) throws IOException {

        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .build();

        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            if (rb.isPresent()) {
                ResponseBody body = rb.get();
                String strBody = body.string();
                try {
                    // LOGGER.debug("http resp string:" + strBody);
                    return GSON.fromJson(strBody, typeToken.getType());
                } catch (Exception e) {
                    throw new RuntimeException("can not handle http response to json:" + body.string(), e);
                }
            } else {
                return null;
            }
        }

    }


    /**
     * GET请求返回应答体(byte[]格式).
     * <p>
     *
     * @param url       目标url.
     * @param headers   请求HTTP头.
     * @return 返回值.
     * @throws IOException Http请求异常.
     */
    public static byte[] getData(String url, Map<String, String> headers) throws IOException {

        if(headers==null){
            headers = new HashMap<>();
        }

        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .build();

        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            if (rb.isPresent()) {
                ResponseBody body = rb.get();
                return body.bytes();
            } else {
                return new byte[0];
            }
        }

    }

    /**
     * 按照JSON格式解析GET请求返回应答体.
     * <p>
     * 调用方式：
     * getJson(url, new TypeToken&lt;A&lt;B&gt;&gt;(){});
     *
     * @param url       目标url.
     * @param typeToken 返回类型.
     * @param <T>       返回类型.
     * @return 返回值.
     * @throws IOException Http请求异常.
     */
    @SuppressWarnings("unused")
    public static <T> T getJson(String url, TypeToken<T> typeToken) throws IOException {
        return getJson(url, new HashMap<>(), typeToken);

    }

    private static Headers.Builder map2Headers(Map<String, String> headers) {
        Headers.Builder hb = new Headers.Builder();
        Optional.ofNullable(headers).orElse(Collections.emptyMap()).forEach(hb::add);
        return hb;
    }


    public static final MediaType JSON = MediaType.get("application/json;charset=UTF-8");

    /**
     * Post方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为字符串.
     *
     * @param url     目标url.
     * @param headers 请求头.
     * @param req     请求体对象.
     * @param <P>     请求体类型.
     * @return 应答字符串.
     * @throws IOException 请求中发生的IO异常.
     */
    public static <P> String postJsonRecieveString(String url, Map<String, String> headers, P req) throws IOException {


        String reqbody = GSON.toJson(req);

        /*
         * reqbody=StringEscapeUtils.unescapeEcmaScript(reqbody);
         * LogHelper.dev("http req body:"+reqbody);
         */

        RequestBody body = RequestBody.create(JSON, reqbody);
        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .post(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            ResponseBody b = response.body();
            Optional<ResponseBody> rb = Optional.ofNullable(b);
            return rb.isPresent() ? rb.get().string() : "";
        }
    }

    /**
     * Post方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为字符串.
     *
     * @param url 目标url.
     * @param req 请求体对象.
     * @param <P> 请求体类型.
     * @return 应答字符串.
     * @throws IOException 请求中发生的IO异常.
     */
    @SuppressWarnings("unused")
    public static <P> String postJsonRecieveString(String url, P req) throws IOException {
        return postJsonRecieveString(url, Collections.emptyMap(), req);
    }

    /**
     * Post方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为Json格式对象.
     *
     * @param <P>       请求体类型.
     * @param <T>       应答体类型.
     * @param url       目标url.
     * @param headers   请求头.
     * @param req       请求体对象.
     * @param typeToken 应答类型.
     * @return 应答对象.
     * @throws IOException 请求中发生的IO异常.
     */
    public static <P, T> T postJsonRecieveJson(String url, Map<String, String> headers, P req, TypeToken<T> typeToken) throws IOException {
        RequestBody body = RequestBody.create(JSON, GSON.toJson(req));
        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .post(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            if (rb.isPresent()) {

                String strBody = rb.get().string();
                try {
                    // LOGGER.info("http resp string:" + strBody);
                    return GSON.fromJson(strBody, typeToken.getType());
                } catch (Exception e) {
                    throw new RuntimeException("can not handle http response to json:" + strBody, e);
                }

            } else {
                return null;
            }
        }
    }

    /**
     * Post方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为Json格式对象.
     *
     * @param <P>       请求体类型.
     * @param <T>       应答体类型.
     * @param url       目标url.
     * @param req       请求体对象.
     * @param typeToken 应答类型.
     * @return 应答对象.
     * @throws IOException 请求中发生的IO异常.
     */
    @SuppressWarnings("unused")
    public static <P, T> T postJsonRecieveJson(String url, P req, TypeToken<T> typeToken) throws IOException {
        return postJsonRecieveJson(url, Collections.emptyMap(), req, typeToken);
    }

    /**
     * PUT方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为字符串.
     *
     * @param url     目标url.
     * @param headers 请求头.
     * @param req     请求体对象.
     * @param <P>     请求体类型.
     * @return 应答字符串.
     * @throws IOException 请求中发生的IO异常.
     */
    public static <P> String putJsonRecieveString(String url, Map<String, String> headers, P req) throws IOException {
        RequestBody body = RequestBody.create(JSON, GSON.toJson(req));
        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .put(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            return rb.isPresent() ? rb.get().string() : "";
        }
    }

    /**
     * put方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为字符串.
     *
     * @param url 目标url.
     * @param req 请求体对象.
     * @param <P> 请求体类型.
     * @return 应答字符串.
     * @throws IOException 请求中发生的IO异常.
     */
    @SuppressWarnings("unused")
    public static <P> String putJsonRecieveString(String url, P req) throws IOException {
        return putJsonRecieveString(url, Collections.emptyMap(), req);
    }

    /**
     * Put方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为Json格式对象.
     *
     * @param <P>       请求体类型.
     * @param <T>       应答体类型.
     * @param url       目标url.
     * @param headers   请求头.
     * @param req       请求体对象.
     * @param typeToken 应答类型.
     * @return 应答对象.
     * @throws IOException 请求中发生的IO异常.
     */
    public static <P, T> T putJsonRecieveJson(String url, Map<String, String> headers, P req, TypeToken<T> typeToken) throws IOException {
        RequestBody body = RequestBody.create(JSON, GSON.toJson(req));
        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .put(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            if (rb.isPresent()) {
                String strBody = rb.get().string();
                try {
                    // LOGGER.info("http resp string:" + strBody);
                    return GSON.fromJson(strBody, typeToken.getType());
                } catch (Exception e) {
                    throw new RuntimeException("can not handle http response to json:" + strBody, e);
                }
            } else {
                return null;
            }
        }
    }

    /**
     * put方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为Json格式对象.
     *
     * @param <P>       请求体类型.
     * @param <T>       应答体类型.
     * @param url       目标url.
     * @param req       请求体对象.
     * @param typeToken 应答类型.
     * @return 应答对象.
     * @throws IOException 请求中发生的IO异常.
     */
    @SuppressWarnings("unused")
    public static <P, T> T putJsonRecieveJson(String url, P req, TypeToken<T> typeToken) throws IOException {
        return putJsonRecieveJson(url, Collections.emptyMap(), req, typeToken);
    }


    /**
     * Delete方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为字符串.
     *
     * @param url     目标url.
     * @param headers 请求头.
     * @param req     请求体对象.
     * @param <P>     请求体类型.
     * @return 应答字符串.
     * @throws IOException 请求中发生的IO异常.
     */
    public static <P> String deleteJsonRecieveString(String url, Map<String, String> headers, P req) throws IOException {
        RequestBody body = RequestBody.create(JSON, GSON.toJson(req));
        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .delete(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            return rb.isPresent() ? rb.get().string() : "";
        }
    }

    /**
     * Delete方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为字符串.
     *
     * @param url 目标url.
     * @param req 请求体对象.
     * @param <P> 请求体类型.
     * @return 应答字符串.
     * @throws IOException 请求中发生的IO异常.
     */
    @SuppressWarnings("unused")
    public static <P> String deleteJsonRecieveString(String url, P req) throws IOException {
        return deleteJsonRecieveString(url, Collections.emptyMap(), req);
    }

    /**
     * Delete方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为Json格式对象.
     *
     * @param <P>       请求体类型.
     * @param <T>       应答体类型.
     * @param url       目标url.
     * @param headers   请求头.
     * @param req       请求体对象.
     * @param typeToken 应答类型.
     * @return 应答对象.
     * @throws IOException 请求中发生的IO异常.
     */
    public static <P, T> T deleteJsonRecieveJson(String url, Map<String, String> headers, P req, TypeToken<T> typeToken) throws IOException {
        RequestBody body = RequestBody.create(JSON, GSON.toJson(req));
        Request request = new Request.Builder()
                .url(url)
                .headers(map2Headers(headers).build())
                .delete(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            if (rb.isPresent()) {
                String strBody = rb.get().string();
                try {
                    //LOGGER.info("http resp string:" + strBody);
                    return GSON.fromJson(strBody, typeToken.getType());
                } catch (Exception e) {
                    throw new RuntimeException("can not handle http response to json:" + strBody, e);
                }
            } else {
                return null;
            }
        }
    }

    /**
     * Delete方式访问目标url，请求体为Json格式，请求头为headers中内容，应答为Json格式对象.
     *
     * @param <P>       请求体类型.
     * @param <T>       应答体类型.
     * @param url       目标url.
     * @param req       请求体对象.
     * @param typeToken 应答类型.
     * @return 应答对象.
     * @throws IOException 请求中发生的IO异常.
     */
    @SuppressWarnings("unused")
    public static <P, T> T deleteJsonRecieveJson(String url, P req, TypeToken<T> typeToken) throws IOException {
        return deleteJsonRecieveJson(url, Collections.emptyMap(), req, typeToken);
    }

    @SuppressWarnings("unused")
    public static String postFormRecieveString(String url, Map<String, String> req) throws IOException {
        return postFormRecieveString(url, new HashMap<>(), req);
    }


    public static final MediaType FORM = MediaType.get("application/x-www-form-urlencoded; charset=utf-8");

    public static String postFormRecieveString(String url, Map<String, String> headers, Map<String, String> req) throws IOException {
        RequestBody body = RequestBody.create(FORM, buildPostFormBody(req));
        //LOGGER.debug("url:" + url);
        Request request = (new Request.Builder()).url(url).headers(map2Headers(headers).build()).post(body).build();
        Response response = client.newCall(request).execute();

        String res;
        try {
            Optional<ResponseBody> rb = Optional.ofNullable(response.body());
            res = rb.isPresent() ? (rb.get()).string() : "";
        } finally {
            try {
                response.close();
            } catch (Throwable e) {
                LOGGER.warn(e.getLocalizedMessage(), e);
            }
        }

        return res;
    }

    private static String buildPostFormBody(Map<String, String> req) {
        StringBuilder tempParams = new StringBuilder();
        int pos = 0;
        try {
            for (String key : req.keySet()) {
                if (pos > 0) {
                    tempParams.append("&");
                }
                tempParams.append(String.format("%s=%s", key, URLEncoder.encode(req.get(key), "utf-8")));

                pos++;
            }
        } catch (UnsupportedEncodingException e) {
            LOGGER.error(e.getLocalizedMessage(), e);
        }
        return tempParams.toString();
    }
}
